<?php
/**
 * Created by PhpStorm
 * User: 龍zero
 * Date: 2022-04-15
 * Time: 15:23
 */

namespace App\Module\File;


use App\Exception\FailedException;
use App\Lib\Enum\FileConstant;
use App\Model\LinFile;
use Hyperf\Config\Annotation\Value;
use Hyperf\Context\Context;
use Hyperf\Coroutine\Exception\ParallelExecutionException;
use Hyperf\Coroutine\Parallel;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Filesystem\FilesystemFactory;
use Hyperf\HttpMessage\Upload\UploadedFile;
use App\Exception\FileException;
use function Hyperf\Support\env;

/**
 * Class AbstractUploader
 * @property array $files
 * @package App\Module\File
 */
abstract class AbstractUploader
{

    /**
     * 被允许的文件类型列表
     * @Value("file.include")
     */
    protected $includes;

    /**
     * 不被允许的文件类型列表
     * @Value("file.exclude")
     */
    protected $excludes;

    /**
     * 单个文件的最大字节数
     * @Value("file.single_limit")
     */
    protected $singleLimit;
    /**
     * 多个文件的最大数量
     * @Value("file.total_limit")
     */
    protected $totalLimit;

    /**
     * 文件上传的最大数量
     * @Value("file.nums")
     */
    protected $nums;

    /**
     * 默认上传模式
     * @Value("file.default")
     */
    protected $defaultMode;

    /**
     * 仅支持本地上传的Mime类型
     * 如果默认上传模式未local则该配置无效
     * @Value("file.local_mime")
     */
    protected $localMime;

    /**
     * @Inject()
     * @var FilesystemFactory
     */
    private $uploadFactory;

    public function upload($fileMap)
    {
        #初始化files成员属性
        $this->initMultiFiles($fileMap);
        #检测文件数以及文件
        $this->checkFileMap();
        #上传处理
        return $this->uploadProcess();
    }

    protected function uploadProcess()
    {
        $parallel = new Parallel($this->nums);
        foreach ($this->files as $key => $file) {
            /**
             * @var $file UploadedFile
             */
            $parallel->add(function () use ($file,$key) {
                #验证MD5
                #上传
                $mode = $this->defaultMode;
                if ($this->localMime) {
                    if (in_array($file->getMimeType(), $this->localMime)) {
                        $mode = 'local';
                    }
                }
                return $this->uploadFile($file,$key,$mode);
            });
        }
        try {
            $ret = $parallel->wait();
        } catch (ParallelExecutionException $e) {
            // $e->getResults() 获取协程中的返回值。
            // $e->getThrowables() 获取协程中出现的异常。
            throw new FailedException([
                'message' =>  $e->getThrowables()
            ]);
        }
        return $ret;
    }

    protected function uploadFile($file,$key,$mode)
    {
        $fileMD5 = $this->generateMd5($file);
        $exists = LinFile::where('md5',$fileMD5)->first();
        if($exists){
            return  [
                'id' => $exists->id,
                'key' => $key,
                'path' => $exists->path,
//                'url' => $this->getFileHostByType($exists->type,$exists->path)
                'url' => $exists->url
            ];
        }
        $dirName = date('Ymd');
        $fileName = md5($file->getFileName()) . '.' . $file->getExtension();
        $path =  $dirName . '/' . $fileName;
        $uploadMode = $this->uploadFactory->get($mode);
        /**
         * @var $file UploadedFile
         */
        try {
            if ($mode == 'local') {
                $stream = fopen($file->getRealPath(), 'r+');
                $uploadMode->writeStream(
                    $path,
                    $stream
                );
                fclose($stream);
            } else {
                $uploadMode->write(
                    $path,
                    $file->getStream()
                );
            }
        } catch (\Exception $e) {
            throw new FailedException([
                'message' => $e->getMessage()
            ]);
        }
        $linFile = new LinFile;
        $linFile->name = $fileName;
        $linFile->path = $path;
        $linFile->size = $file->getSize();
        $linFile->extension = $file->getExtension();
        $linFile->md5 = $fileMD5;
        $linFile->type = $mode;
        $linFile->save();

        return  [
            'id' => $linFile->id,
            'key' => $key,
            'path' => $path,
//            'url' => $this->getFileHostByType($linFile->type,$path)
            'url' => $linFile->url
        ];
    }


    protected function initMultiFiles($fileMap)
    {
        if (is_object($fileMap)) {
            #单图上传
            $this->files = [$fileMap];
        } elseif (is_array($fileMap)) {
            #多图上传
            $this->files = $fileMap;
        } else {
            throw new FailedException(10122);
        }
    }

    protected function checkFileMap()
    {
        #检测文件数是否超出
        if (count($this->files) > $this->nums) {
            throw new FailedException(10121);
        }
        $totalSize = 0;
        foreach ($this->files as $file) {
            $size = $this->checkFileMapSize($file);
            $totalSize += $size;
            $this->checkFileFormat($file);
        }
        if ($totalSize > $this->totalLimit) {
            throw new FileException(10110);
        }
    }

    protected function checkFileMapSize($file)
    {
        /**
         * @var $file UploadedFile
         */
        $fileSize = $file->getSize();
        if ($fileSize > $this->singleLimit) {
            throw new FileException(10110);
        }
        return $fileSize;
    }

    /**
     * 检测文件格式
     * @param $file
     */
    protected function checkFileFormat($file)
    {
        /**
         * @var $file UploadedFile
         */
        #验证客户端mime类型与接收到的mime类型是否一致不一致不通过
//        if ($file->getClientMediaType() != $file->getMimeType()) {
//            throw new FileException(10123);
//        }
        $fileExt = $file->getExtension();

        if ($this->includes) {
            #存在允许文件扩展 则黑名单无效
            if (!in_array($fileExt, $this->includes)) {
                throw new FileException(10130);
            }
        } elseif ($this->excludes) {
            if (in_array($fileExt, $this->excludes)) {
                throw new FileException(10130);
            }
        }
    }

    protected function getFileHostByType($fileType,$path){
        if($fileType == FileConstant::LOCAL){
            $url = env('DOMAIN').env('FILE_STORE_DIR').'/'.$path;
        }else{
            $url = env('IMG_DOMAIN').env('FILE_STORE_DIR').'/'.$path;
        }
        return $url;
    }

    /**
     * @param $file
     * @return string
     */
    protected function generateMd5($file)
    {
        /**
         * @var $file UploadedFile
         */
        $md5 = md5_file($file->getPathname());
        return $md5;
    }


    public function __get($name)
    {
        return Context::get($name);
    }

    public function __set($name, $value)
    {
        Context::set($name, $value);
    }

}